package com.pin.si.www.framework.aspect;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.ContentCachingRequestWrapper;

import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * 请求日志切面类
 * 功能说明：
 * 1. 记录所有Controller层方法的请求和响应信息
 * 2. 支持异步日志记录，不影响主流程性能
 * 3. 自动处理请求参数和响应体的格式化
 * 4. 包含请求耗时统计功能
 *-
 * 使用方式：
 * 1. 自动生效，无需额外配置
 * 2. 会拦截所有带有@Controller或@RestController注解的类中的方法
 *-
 * 日志格式示例：
 * [请求方法] 请求URL - 状态码 (耗时ms)
 * 参数: {key1=value1, key2=value2}
 * 请求体: {...}
 * 响应: {...}
 *-
 * 性能优化：
 * - 使用虚拟线程处理耗时的日志记录操作
 * - 对请求体和响应体进行长度限制
 * - 异步记录日志，不阻塞主流程
 *
 * @author bin.1024@qq.com
 * @since 1.0.0
 */
@Aspect
@Component
@Slf4j
public class RequestLogAspect {

    private static final int MAX_BODY_LENGTH = 10240; // 10KB截断阈值
    private static final int MAX_PARAM_VALUE_LENGTH = 1024; // 1KB

    // 请求日志相关常量
    private static final String REQUEST_IP = "Request-IP: ";
    private static final String REQUEST_PATH = ", Path: ";
    private static final String REQUEST_PARAMS = ", Params: ";
    private static final String REQUEST_BODY = ", Body: ";
    private static final String STATUS = " | Status: ";
    private static final String TIME_MS = "ms";
    private static final String TIME_PREFIX = ", Time: ";
    private static final String RESPONSE = ", Response: ";
    private static final String TRUNCATED_SUFFIX = "...(truncated)";
    private static final String SUCCESS = "Success";
    private static final String MULTIPART_PREFIX = "multipart/";
    private static final String SPACE = " ";

    // 优化JSON序列化配置
    private static final ObjectMapper objectMapper = new ObjectMapper()
            .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
            .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
            .enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);

    // 使用虚拟线程执行器
    private final ExecutorService virtualThreadExecutor;

    @Autowired
    public RequestLogAspect(ExecutorService virtualThreadExecutor) {
        this.virtualThreadExecutor = virtualThreadExecutor;
    }

    @Pointcut("@within(org.springframework.web.bind.annotation.RestController) || " +
            "@within(org.springframework.stereotype.Controller)")
    public void controllerPointcut() {}

    /**
     * 环绕通知方法，用于记录请求日志
     * 主要功能：
     * 1. 记录请求开始时间
     * 2. 收集请求信息（URL、方法、参数等）
     * 3. 执行目标方法
     * 4. 记录响应结果和耗时
     * 5. 异常处理和日志记录
     * 性能优化：
     * - 使用虚拟线程并行处理耗时的日志收集操作
     * - 对请求体和响应体进行长度限制，避免内存溢出
     * - 异步记录日志，不影响主流程性能
     *
     * @param joinPoint 连接点，包含目标方法的信息
     * @return 目标方法的执行结果
     * @throws Throwable 目标方法可能抛出的异常
     */
    @Around("controllerPointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            return joinPoint.proceed();
        }

        HttpServletRequest request = attributes.getRequest();

        // 使用虚拟线程并行处理请求解析（并行处理）
        Future<Map<String, String>> paramsFuture = virtualThreadExecutor.submit(() -> getRequestParams(request));
        Future<String> bodyLogFuture = virtualThreadExecutor.submit(() -> getRequestBodyForLog(request));
        Future<String> clientIpFuture = virtualThreadExecutor.submit(() -> getClientIp(request));

        long startTime = System.nanoTime();
        // 执行实际业务方法
        Object result = joinPoint.proceed();
        long costTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);

        virtualThreadExecutor.execute(() -> {
            StringBuilder baseLog = new StringBuilder(512);
            try {
                // 使用虚拟线程处理响应体格式化（并行处理）
                Future<String> responseLogFuture = virtualThreadExecutor.submit(() -> formatResponseBody(result));

                // 构建请求基础日志
                String clientIp = clientIpFuture.get();
                baseLog.append(REQUEST_IP).append(clientIp)
                        .append(REQUEST_PATH).append(request.getMethod()).append(SPACE).append(request.getRequestURI());

                Map<String, String> params = paramsFuture.get();
                if (!params.isEmpty()) {
                    baseLog.append(REQUEST_PARAMS).append(params);
                }

                String bodyLog = bodyLogFuture.get();
                if (!bodyLog.isEmpty()) {
                    baseLog.append(REQUEST_BODY).append(bodyLog);
                }

                // 开始响应体合并，重用StringBuilder减少对象创建
                baseLog.append(STATUS).append(SUCCESS)
                        .append(TIME_PREFIX).append(costTime).append(TIME_MS);

                // 响应体格式化任务
                if (result != null) {
                    String responseLog = responseLogFuture.get();
                    baseLog.append(RESPONSE).append(responseLog);
                }

                // 记录日志构建完成时间
                long logBuildTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) - costTime;
                baseLog.append(", LogBuildTime: ").append(logBuildTime).append(TIME_MS);

                // 打印日志
                log.info(baseLog.toString());

            } catch (Exception e) {
                log.error("记录请求日志异常", e);
            }
        });

        return result;
    }

    /**
     * 获取客户端真实IP地址
     * 注意：由于可能存在代理服务器，需要从多个HTTP头中获取真实的客户端IP
     * 检查顺序：
     * 1. X-Forwarded-For: 这是代理服务器添加的，包含客户端和各级代理的IP
     * 2. X-Real-IP: 某些代理服务器会设置此头
     * 3. Proxy-Client-IP: Apache服务器的代理
     * 4. WL-Proxy-Client-IP: WebLogic服务器的代理
     * 5. 最后才使用request.getRemoteAddr()获取直接连接的客户端IP
     *
     * @param request HTTP请求对象
     * @return 客户端的真实IP地址，如果无法获取则返回"unknown"
     */
    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip != null && !ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
            return ip.split(",")[0];
        }
        return request.getRemoteAddr();
    }

    /**
     * 获取HTTP请求参数
     * 此方法会收集请求中的所有参数，包括URL查询参数和表单参数
     * 对于文件上传请求，只记录文件名和大小，不记录文件内容
     * 对于JSON请求体，建议在getRequestBodyForLog方法中处理
     *
     * @param request HTTP请求对象
     * @return 包含所有请求参数的Map，key为参数名，value为参数值。
     *         对于多值参数，多个值之间用逗号分隔。
     *         对于文件上传，value格式为"[文件名] (大小: X)"
     */
    private Map<String, String> getRequestParams(HttpServletRequest request) {
        Map<String, String> params = new HashMap<>();
        Enumeration<String> paramNames = request.getParameterNames();
        while (paramNames.hasMoreElements()) {
            String name = paramNames.nextElement();
            String value = request.getParameter(name);
            if (value != null && value.length() > MAX_PARAM_VALUE_LENGTH) {
                value = value.substring(0, MAX_PARAM_VALUE_LENGTH) + TRUNCATED_SUFFIX;
            }
            params.put(name, value);
        }
        return params;
    }


    /**
     * 获取请求体内容用于日志记录
     * 此方法会处理各种类型的请求体：
     * 1. 对于multipart/form-data类型的请求，返回文件上传信息
     * 2. 对于JSON/XML等文本类型的请求，返回请求体内容（限制长度）
     * 3. 对于其他二进制类型的请求，返回内容长度信息
     * 注意：
     * - 会自动包装请求以支持重复读取body内容
     * - 对于大文件上传，只会记录基本信息而不会记录文件内容
     * - 会限制返回的body内容长度，避免日志过大
     *
     * @param request HTTP请求对象
     * @return 格式化后的请求体内容，用于日志记录
     */
    private String getRequestBodyForLog(HttpServletRequest request) {
        // 包装非multipart请求以便重复读取body
        if (!(request instanceof ContentCachingRequestWrapper)) {
            String contentType = request.getContentType();
            if (contentType == null || !contentType.startsWith(MULTIPART_PREFIX)) {
                request = new ContentCachingRequestWrapper(request);
            }
        }

        if (isMultipart(request)) {
            // 使用新的formatBytes方法直接显示字节数
            return "<Multipart> " + formatBytes(request.getContentLengthLong());
        }

        if (request instanceof ContentCachingRequestWrapper wrapper) {
            byte[] content = wrapper.getContentAsByteArray();
            if (content.length == 0) return "";

            // 非文件类型请求体仍然使用自动转换的大小显示
            if (content.length > MAX_BODY_LENGTH) {
                return "<Body> " + formatSize(content.length);
            }

            try {
                return new String(content, wrapper.getCharacterEncoding());
            } catch (Exception e) {
                return "<EncodingError>";
            }
        }
        return "";
    }

    /**
     * 判断当前请求是否为文件上传请求
     * 通过检查Content-Type请求头是否以"multipart/"开头来判断
     * 这是处理文件上传的标准方式，符合HTTP协议规范
     *
     * @param request HTTP请求对象
     * @return 如果请求是multipart类型（如文件上传）返回true，否则返回false
     */
    private boolean isMultipart(HttpServletRequest request) {
        String contentType = request.getContentType();
        return contentType != null && contentType.startsWith(MULTIPART_PREFIX);
    }


    /**
     * 格式化响应体内容用于日志记录
     * 此方法会根据响应体的不同类型进行格式化处理：
     * 1. 对于null值，返回"null"
     * 2. 对于字符串类型，直接返回（限制长度）
     * 3. 对于字节数组，返回其长度信息
     * 4. 对于其他对象，使用JSON序列化（限制长度）
     * 注意：
     * - 会限制返回的内容长度，避免日志过大
     * - 对于大对象，会进行截断处理
     * - 序列化异常时会返回错误信息
     *
     * @param body 响应体对象
     * @return 格式化后的响应体字符串，用于日志记录
     */
    private String formatResponseBody(Object body) {
        if (body == null) return "null";

        // 解包ResponseEntity
        Object target = body;
        while (target instanceof ResponseEntity) {
            target = ((ResponseEntity<?>) target).getBody();
        }
        switch (target) {
            case null -> {
                return "null";
            }
            // 处理特殊类型 - 使用新的formatBytes方法
            case byte[] bytes -> {
                return "<Binary> " + formatBytes(bytes.length);
            }
            case Resource resource -> {
                try {
                    // 处理InputStreamResource等不可获取大小的类型
                    long size = resource.contentLength();
                    return "<Resource> " + formatBytes(size);
                } catch (IOException | IllegalArgumentException e) {
                    return "<Resource (size unknown)>";
                }
            }
            case MultipartFile file -> {
                return "<MultipartFile> " + formatBytes(file.getSize());
            }
            // 字符串类型直接返回（避免JSON序列化）
            case String str -> {
                return truncateIfNeeded(str);
            }
            default -> {
            }
        }

        // 普通对象JSON序列化
        try {
            return truncateIfNeeded(objectMapper.writeValueAsString(target));
        } catch (JsonProcessingException e) {
            return truncateIfNeeded(target.toString());
        }
    }

    /**
     * 字符串截断处理
     * 当字符串超过最大长度限制时，会进行截断并添加省略号
     * 如果输入为null，则返回"null"字符串
     * 使用场景：
     * - 日志内容过长时进行截断，避免日志文件过大
     * - 敏感信息脱敏处理前的长度检查
     *
     * @param str 需要处理的字符串
     * @return 处理后的字符串，如果输入为null则返回"null"
     *         如果字符串长度超过MAX_BODY_LENGTH，则截断并添加"..."
     */
    private String truncateIfNeeded(String str) {
        if (str.length() <= MAX_BODY_LENGTH) return str;
        return str.substring(0, MAX_BODY_LENGTH) + TRUNCATED_SUFFIX;
    }

    /**
     * 直接格式化字节数为字节单位
     */
    private String formatBytes(long bytes) {
        if (bytes < 0) return "unknown size";
        return bytes + " bytes";
    }

    /**
     * 格式化字节大小为友好字符串（自动转换为KB/MB）
     * 用于非文件类型的请求体显示
     */
    private String formatSize(long bytes) {
        if (bytes <= 0) return "0 bytes";
        if (bytes < 1024) return bytes + " bytes";

        double kb = bytes / 1024.0;
        if (kb < 1024) return String.format("%.1f KB", kb);

        double mb = kb / 1024.0;
        return String.format("%.1f MB", mb);
    }
}